package org.jmetrix.group;

import org.jmetrix.api.Aggregator;
import org.jmetrix.api.Measurement;
import org.jmetrix.et.ExecutionTime;
import org.jmetrix.et.ExecutionTimeAggregator;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author Konstantin Kuzmin
 * @version $Revision$
 * @since 04.10.11
 */
public class GroupAggregationTree implements Aggregator<GroupMeasurement> {
    protected Map<String, GroupAggregationTree> subTree = new LinkedHashMap<String, GroupAggregationTree>();
    protected Map<Class<? extends Measurement>, Aggregator> plainAggregators = new LinkedHashMap<Class<? extends Measurement>, Aggregator>();

    public GroupAggregationTree() {
    }

    public Map<String, GroupAggregationTree> getSubTree() {
        return subTree;
    }

    public Collection<Aggregator> getAggregators() {
        return plainAggregators.values();
    }

    @Override
    public void aggregate(final GroupMeasurement measurement) {
        GroupAggregationTree groupSubTree = this;

        for (final String group : measurement.getGroups()) {
            if (!groupSubTree.subTree.containsKey(group)) {
                groupSubTree.subTree.put(group, new GroupAggregationTree());
            }
            groupSubTree = groupSubTree.subTree.get(group);
        }
        groupSubTree.getAggregator(measurement).aggregate(measurement);
    }

    public Aggregator getAggregator(final Measurement measurement) {
        final Class<? extends Measurement> measurementClass = measurement.getClass();
        Aggregator aggregator = plainAggregators.get(measurementClass);
        if (aggregator == null) {
            aggregator = createAggregator(measurementClass);
            if (aggregator == null) {
                throw new IllegalArgumentException("Can not instantiate aggregator - unsupported measurement class: " + measurementClass.getName());
            }
            plainAggregators.put(measurementClass, aggregator);
        }
        return aggregator;
    }

    protected Aggregator createAggregator(final Class<? extends Measurement> measurementClass) {
        // TODO provide some plugable approach to add new aggregators
        if (ExecutionTime.class.isAssignableFrom(measurementClass)) {
            return new ExecutionTimeAggregator();
        }
        return null;
    }

    @Override
    public void reset() {
        resetRecursively(this);
    }

    private static void resetRecursively(final GroupAggregationTree aggregationTree) {
        final Collection<Aggregator> aggregators = new ArrayList<Aggregator>(aggregationTree.plainAggregators.values());
        for (final Aggregator aggregator : aggregators) {
            aggregator.reset();
        }
        final Collection<GroupAggregationTree> copySubTree = new ArrayList<GroupAggregationTree>(aggregationTree.subTree.values());
        for (final GroupAggregationTree subTree : copySubTree) {
            resetRecursively(subTree);
        }
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        recursiveToString("", this, 0, "    ", sb);
        return sb.toString();
    }

    protected void recursiveToString(final String name, GroupAggregationTree groupAggregationTree, final int indent, String indentString, StringBuilder sb) {
        final String nl = System.getProperty("line.separator");
        append(sb, indent, indentString).append(name).append(" {").append(nl);
        for (final Aggregator aggregator : groupAggregationTree.plainAggregators.values()) {
            append(sb, indent, indentString).append(aggregator).append(nl);
        }
        for (Map.Entry<String, GroupAggregationTree> subTreeEntry : groupAggregationTree.subTree.entrySet()) {
            recursiveToString(subTreeEntry.getKey(), subTreeEntry.getValue(), indent + 1, indentString, sb);
        }
        append(sb, indent, indentString).append("}").append(nl);
    }

    private StringBuilder append(StringBuilder sb, int indent, String indentString) {
        for (int j = 0; j < indent; j++) {
            sb.append(indentString);
        }
        return sb;
    }
}
